#ifndef	_Packer_H_
#define	_Packer_H_

#include <string>
#include <vector>
#include <algorithm>
#include <numeric>

#include <boost/random/uniform_real_distribution.hpp>
#include <boost/random/random_number_generator.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/variate_generator.hpp>

#include <CGAL/Aff_transformation_2.h>
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Boolean_set_operations_2.h>
#include <CGAL/Timer.h>

#ifdef _CILK_
#include <cilk/cilk.h>
#endif

#include <Geex/basics/assert.h>

#include <Geex/basics/file_system.h>
#include <glut_viewer/glut_viewer.h>

#include "knitro.h"

#include "../common/containers.h"
#include "triangulation.h"
#include "containment.h"
#include "optimizer.h"
#include "swapper.h"
#include "../common/bin_packing.h"
#include "../common/circle_union.h"
#include "../common/potential_energy.h"
#include "../common/circle_system.h"

namespace Packing 
{

	using namespace Packing_common;
	class Packing_graphics;
	typedef CGAL::Exact_predicates_exact_constructions_kernel Exact_K;

	class Packer 
	{
		friend class Packing_graphics;
		
	public:
		
		Packer(const std::string& poly_fn, double coverage_rate = 0.7) ;
		~Packer();
		/* set methods */
		bool& toggle_enlarge() { return allow_enlarge; }
		bool& toggle_use_voronoi() { return use_voronoi; }
		bool& toggle_swap() { return allow_swap; }
		double& set_max_rate() { return max_rate; }
		/* query methods */
		static Packer* instance() { return instance_ ; }
		inline unsigned int nb_polytopes() const { return polygon_set.size(); }

		/* debug */
		void swap() { swapper->random_shuffle(); update_dt();}

		/* pack methods */
		void lloyd(unsigned int nb_iter, void (*post_action)() = NULL) ;
		void pack(void (*post_action)() = NULL);
		void sa_pack(void (*post_action)() = NULL); // pack with simulated annealing
		//void transform_one_polytope(unsigned int id, const Parameter& p);
		void shrink( double factor = 1.0 );

		void optimize() ; // optimize the energy

		/* miscellaneous */
		void get_bbox(Geex::real& x_min, Geex::real& y_min, Geex::real& z_min, Geex::real& x_max, Geex::real& y_max, Geex::real& z_max);

	private:
		//void one_lloyd(bool enlarge);
		//bool constrain_transformation(Constraint_type type, unsigned int id, Parameter& p);
		void update_dt();
		double coverage_rate(double& max_factor, double& min_factor) const;
		inline bool should_stop(double coverage_rate, double minf)
		{
			return std::fabs(coverage_rate - max_rate) <= 1.0e-6 || minf >= 1.0;
		}
	private:
		static Packer* instance_ ;

		//geometric objects
		Container_box *cbox;
		std::vector<Packing_polygon*> polygon_set;
		Polygon_triangulation *pt;
		// statistic
		double total_volume;
		double max_rate;
		Optimizer *optimizer;
		Swapper *swapper;

		bool allow_enlarge;
		bool use_voronoi;
		bool allow_swap;

		// energy objects
		std::vector<Circle_union> cus;
	} ;
}

#endif